// Copyright 2018 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License included
// in the file licenses/BSL.txt and at www.mariadb.com/bsl11.
//
// Change Date: 2022-10-01
//
// On the date above, in accordance with the Business Source License, use
// of this software will be governed by the Apache License, Version 2.0,
// included in the file licenses/APL.txt and at
// https://www.apache.org/licenses/LICENSE-2.0

package hba

import (
	"net"
	"strings"
	"unicode/utf8"

	"github.com/pkg/errors"
)

func Parse(input string) (*Conf, error) {
	if !utf8.ValidString(input) {
		return nil, errors.New("invalid UTF-8")
	}
	// To ease parsing, ensure a newline at EOF.
	data := []rune(input + "\n")

	%% machine scanner;
	%% alphtype rune;
	%% write data;

	// These are generated by ragel. Reference them to avoid unused lint errors.
	_, _, _ = scanner_first_final, scanner_error, scanner_en_main

	cs, p, pe, eof := 0, 0, len(data), len(data)

	var (
		mark   int
		ms     []String
		s      String
		ipn    *net.IPNet
		e      Entry
		err    error
		d      string
		option [2]string
		conf   Conf
	)

	%%{
		action mark { mark = p }

		action quotedString {
			s = String{
				Value: string(data[mark:p-1]),
				Quoted: true,
			}
		}
		action string {
			s = String{Value: string(data[mark:p])}
		}
		quotedString =
			'"'
			^'"'* >mark
			'"' %quotedString
			;
		string = ^('"' | space) >mark ^space+ %string;
		stringer =
			quotedString
			| string
			;
		action multiString { ms = append(ms, s) }
		multiString =
			stringer >{ms = nil} %multiString
			(',' stringer %multiString)*
			;

		action addressSlash {
			d = string(data[mark:p])
		}
		action addressSpace {
			d = strings.Join(strings.Fields(string(data[mark:p])), "/")
		}
		action addressIP {
			_, ipn, err = net.ParseCIDR(d)
			if err != nil {
				return nil, err
			}
			e.Address = ipn
		}
		action addressString {
			e.Address = s
		}
		ws = (' ' | '\t')+;
		comment = '#' ^'\n'* '\n';
		address =
				(xdigit | '.' | ':')+
				(
					'/' digit+ %addressSlash
					| ws digit+ %addressSpace
				)
				%addressIP
			|
				# partial support (to avoid parsing ambiguity) for hostname parsing; enough to get 'all'
				(alpha | '-')+ %string %addressString
			;
		method = string;

		action newHost {
			e = Entry{Type: "host"}
		}
		action database {
			e.Database = ms
		}
		action user {
			e.User = ms
		}
		action method {
			e.Method = string(data[mark:p])
		}
		action option {
			copy(option[:], strings.Split(string(data[mark:p]), "="))
			e.Options = append(e.Options, option)
		}
		token = alnum | '.' | '_' | '-';
		option =
			token+ >mark
			'='
			token+ %option
			;
		action host {
			conf.Entries = append(conf.Entries, e)
		}
		host =
			'host' %newHost ws
			multiString %database ws
			multiString %user ws
			address >mark ws
			method >mark %method
			(
				ws
				option >mark %option
			)*
			ws? (comment | '\n')
			;
		action invalidHost { return nil, errors.Errorf("entry %d invalid", len(conf.Entries) + 1) }
		top =
			space
			| comment
			| host %host @err(invalidHost)
			;
		action invalid { return nil, errors.New("invalid") }
		main :=
			top**
			%err(invalid)
			;

		write init;
		write exec;
	}%%

	if len(conf.Entries) == 0 {
		return nil, errors.New("no entries")
	}

	return &conf, nil
}
